﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Platform.Storage.Entity;
using FastMember;
using Platform.Storage.Impl;
using Platform.ObjectModel.Design;
using System.Diagnostics;
using Platform.Infrastructure;
using Platform.ObjectModel;

namespace Platform.Linq
{

	/// <summary>
	/// Realiza a execução da pesquisa
	/// </summary>
	public sealed class DynamicQueryExecutor // TODO : Mover para o namespace Platform.Storage
	{
		//private SymbolTable symbols = new SymbolTable();

		/// <summary>
		/// 
		/// </summary>
		/// <param name="materializer"></param>
		internal DynamicQueryExecutor( Library library )
		{
			//Debug.Assert( materializer != null, "O materializador do documento não foi informado." );
			Debug.Assert( library != null, "A biblioteca de documentos não foi informada." );

			this.Library = library;
			this.Translator = new EntityQueryTranslator();
			this.Materializer = new EntityDataObjectMaterializer( library );
		}

		/// <summary>
		/// Obtém ou define o objeto que transforma os dados de chave e valor em um documento
		/// </summary>
		public EntityQueryTranslator Translator
		{ get; private set; }

		/// <summary>
		/// Objeto que irá realiza a materialização dos dados selecionados
		/// </summary>
		public EntityDataObjectMaterializer Materializer
		{ get; private set; }

		/// <summary>
		/// Obtém a biblioteca de documentos
		/// </summary>
		public Library Library
		{ get; private set; }

		/// <summary>
		/// Obtém ou define quantos níveis do grafo serão carregados junto com os dados 
		/// dos documentos selecionados
		/// </summary>
		/// <remarks>Atualmente somente um nível pode ser carregado</remarks>
		public int EagerLoadLevel
		{ get; set; }

		/// <summary>
		/// Realiza a tradução e mapeamento dos dados
		/// </summary>
		/// <typeparam name="TResult"></typeparam>
		/// <param name="query"></param>
		/// <returns></returns>
		public TResult Execute<TResult>( Expression query )
		{
			Arguments.IsNotNull( "query", query );
			Arguments.IsInRange( "EagerLoadLevel", this.EagerLoadLevel, 0, 1 );

			// Tradução da expressão lambda
			var translated = this.Translator.Translate( query );

			if ( translated.Type == typeof( IQueryable<FieldValue> ) )
			{
				// TODO : Remover a compilação desse passo
				var lambda = Expression.Lambda<Func<IQueryable<FieldValue>>>( translated );
				// Realiza a pesquisa
				var items = lambda.Compile()();

				// Cria o objeto para realizar a enumeração
				Type resultType = typeof( TResult );
				Type itemType = null;

				var context = this.Translator.Context;

				if ( resultType.IsEnumerable() )
				{
					itemType = resultType.GetGenericArguments()[ 0 ];
					return ( TResult )Activator.CreateInstance( typeof( DynamicQueryEnumerable<> ).MakeGenericType( itemType ), this.Materializer, items, this.GetRelationsEnumerator( items, context ) );
				}
				else
				{
					var enumerator = new DynamicQueryEnumerable<TResult>( this.Materializer, items, this.GetRelationsEnumerator( items, context ) );
					return enumerator.FirstOrDefault();
				}
			}
			else
			{
				var lambda = Expression.Lambda<Func<TResult>>( translated );
				return lambda.Compile()();
			}
		}


		private RelationshipEnumerator GetRelationsEnumerator( IQueryable<FieldValue> parentFields, Context translatorContext)
		{
			if ( this.EagerLoadLevel > 0 )
			{
				return RelationshipEnumerator.Create( this.Materializer, translatorContext.EntityType, translatorContext.Resolver, parentFields );
			}
			else
			{
				return new RelationshipEnumerator();
			}
		}

		/// <summary>
		/// Obtém os documentos relacionados aos documentos retornados pela query <paramref name="parentFields"/>
		/// </summary>
		/// <param name="parentFields"></param>
		/// <returns></returns>
		//private IEnumerable<DocumentFlake> GetRelatedDocuments( IQueryable<FieldValue> parentFields )
		//{
		//	if ( this.EagerLoadLevel > 0 )
		//	{
				//IQueryable<DataObjectRelationship> relations = resolver.Relations;
				//IQueryable<FieldValue> childValues = resolver.Fields;

				//Schema schema;

				//if ( !resolver.TryGetSchema( entityType, out schema ) )
				//{
				//    string keyField = schema.QualifiedName.FieldNameWithSchema( schema.FieldKey ).ToLowerInvariant();
				//    string schemaName = schema.QualifiedName.FullName.ToLowerInvariant();

				//    var query = ( from relation in relations
				//                  join childValue in childValues on relation.ChildReversedTokenId equals childValue.ReversedTokenId
				//                  where relation.ParentLoweredFieldName.StartsWith( schemaName )
				//                     && parentFields.Where( pf => pf.LoweredFieldName == keyField ).Any( pf => pf.ReversedTokenId == relation.ParentReversedTokenId )
				//                  orderby relation.ParentReversedTokenId, relation.ParentLoweredFieldName, relation.Order
				//                  select childValue );
				//}
		//	}
		//	else
		//	{
		//		return Enumerable.Empty<DocumentFlake>();
		//	}
		//}

		/// <summary>
		/// Carrega os dados dos documentos relacionados ao documentos carregados em <paramref name="parents"/>
		/// </summary>
		/// <param name="entityType"></param>
		/// <param name="parents"></param>
		/// <param name="resolver"></param>
		//private void LoadRelationsFrom( Type entityType, IQueryable<FieldValue> parents, IQueryResolver resolver )
		//{
		//	IQueryable<DataObjectRelationship> relations = resolver.Relations;
		//	IQueryable<FieldValue> childValues = resolver.Fields;

		//	Schema schema;
			
		//	if ( !resolver.TryGetSchema( entityType, out schema ) )
		//	{
		//		string keyField = schema.QualifiedName.FieldNameWithSchema( schema.FieldKey ).ToLowerInvariant();
		//		string schemaName = schema.QualifiedName.FullName.ToLowerInvariant();

		//		var query = ( from relation in relations
		//					  join childValue in childValues on relation.ChildReversedTokenId equals childValue.ReversedTokenId
		//					  where relation.ParentLoweredFieldName.StartsWith( schemaName )
		//						 && parents.Where( pf => pf.LoweredFieldName == keyField ).Any( pf => pf.ReversedTokenId == relation.ParentReversedTokenId )
		//					  orderby relation.ParentReversedTokenId, relation.ParentLoweredFieldName, relation.Order
		//					  select childValue );
		//	}
		//}
	}
}
